﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using System.Collections.Generic;
using Numbers.Transactions;

namespace Numbers.Arithmatic
{
    public class Addition
    {
        private Value _r;
        private Value[] _add;

        public Addition(Value result, Value[] addends)
        {
            this._r = result;
            this._add = addends;

            ///
            /// First, make sure everything is consistent! If we can't
            /// even set it up like that, then we blow out of here - we'll
            /// never get a consistent framework if we don't.
            /// 

            double expectedValue = CalculateSum();
            _r.Val = expectedValue;

            if (expectedValue != _r.Val)
            {
                throw new ArgumentException("Unable to make things add up and still be with in the rails of the result!");
            }

            ///
            /// Now, wire up to monitor for changes
            /// 

            result.ProposedPropertyChange += new Numbers.Transactions.ProposedPropertyChangeCallback(result_ProposedPropertyChange);

            foreach (var a in addends)
            {
                a.ProposedPropertyChange += new Numbers.Transactions.ProposedPropertyChangeCallback(a_ProposedPropertyChange);
            }

            ///
            /// Finally, make sure they are all part of the same transaction!
            /// 

            List<Value> allValues = new List<Value>();
            allValues.Add(result);
            foreach (var av in addends)
            {
                allValues.Add(av);
            }
            Manager.ManageByOneManager(allValues.ToArray());
        }

        /// <summary>
        /// Runs through everything and calculates the sum.
        /// </summary>
        /// <returns></returns>
        private double CalculateSum()
        {
            return _add.Sum(a => a.Val);
        }

        /// <summary>
        /// Creates a new addition relation between a result and the values.
        /// </summary>
        /// <param name="_lightcharm"></param>
        /// <param name="value"></param>
        public static void Create(Value result, Value[] addends)
        {
            new Addition(result, addends);
        }

        /// <summary>
        /// One of the addons changed. In this case only the result changes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        bool a_ProposedPropertyChange(Numbers.Transactions.IObjectInTransaction obj, string propName)
        {
            _r.Val = CalculateSum();
            return true;
        }

        /// <summary>
        /// The result changed. We need to back-propagate everything to
        /// the addends. A bit of a mess.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        bool result_ProposedPropertyChange(Numbers.Transactions.IObjectInTransaction obj, string propName)
        {
            Value.DisableNotifications[] allChanged = null;
            if (_r.Val == 0)
            {
                ///
                /// Special case of setting teh main value to zero - we assume that
                /// there are no negative numbers. :-)
                ///

                allChanged = (from a in _add
                              select new Value.DisableNotifications(a, 0.0)).ToArray();
            }
            else
            {
                ///
                /// Ok - for some non-zero value attempt to keep everything fractionally the same.
                /// 

                double total = CalculateSum();
                double delta = _r.Val - total;
                if (delta != 0.0)
                {
                    if (total == 0)
                    {
                        ///
                        /// Special case when this is an empty sum - split evenly.
                        /// 

                        double frac = delta / _add.Length;
                        allChanged = (from a in _add
                                      select new Value.DisableNotifications(a, a.Val + frac)).ToArray();
                    }
                    else
                    {
                        var amountToChange = from a in _add
                                             select new
                                             {
                                                 Change = delta * (a.Val / total),
                                                 Add = a
                                             };

                        ///
                        /// Shut off property notifications while we do this update - we sort-of need an atomic
                        /// change to do this right!
                        /// 

                        allChanged = (from a in amountToChange
                                      select new Value.DisableNotifications(a.Add, a.Add.Val + a.Change)).ToArray();
                    }
                }
            }

            ///
            /// Turn notifications back on. Hopefully we won't make it this far again!
            /// 

            if (allChanged != null)
            {
                foreach (var a in allChanged)
                {
                    a.Dispose();
                }
            }


            return true;
        }
    }
}
